﻿using System;
using System.Collections.Generic;

namespace GLDotNet
{
    public class GraphicsDevice : DisposableObject
    {
        Dictionary<BufferTarget, uint> buffers;
        Dictionary<uint, Dictionary<TextureTarget, uint>> textures;
        uint vertexArray;
        uint activeTexture;
        
        Viewport viewport;
        Stack<Viewport> scissorBoxStack;
        
        public GLContext Context
        {
            get;
            private set;
        }

        public bool IsContextDisposed
        {
            get { return this.Context.IsDisposed; }
        }
                
        public Viewport Viewport
        {
            get { return this.viewport; }

            set
            {
                if (value != this.viewport)
                {
                    this.viewport = value;
                    this.Context.Viewport(value.X, value.Y, value.Width, value.Height);
                }
            }
        }
               
        public GraphicsDevice(GLContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            this.Context = context;
            
            this.buffers = new Dictionary<BufferTarget, uint>();
            this.textures = new Dictionary<uint, Dictionary<TextureTarget, uint>>();

            this.scissorBoxStack = new Stack<Viewport>();
        }

        protected override void DisposeManagedResources()
        {
            this.Context = null;
        }

        protected override void DisposeUnmanagedResources()
        {
        }

        internal void BindBuffer(BufferTarget target, uint buffer)
        {
            if (!this.buffers.ContainsKey(target) || this.buffers[target] != buffer)
            {
                this.Context.BindBuffer((uint)target, (uint)buffer);
                this.buffers[target] = buffer;
            }
        }

        internal void BindTexture(TextureTarget target, uint texture)
        {
            if (!this.textures.ContainsKey(this.activeTexture))
            {
                this.textures[this.activeTexture] = new Dictionary<TextureTarget, uint>();
            }

            if (!this.textures[this.activeTexture].ContainsKey(target) || this.textures[this.activeTexture][target] != texture)
            {
                this.Context.BindTexture((uint)target, (uint)texture);
                this.textures[this.activeTexture][target] = texture;
            }
        }

        internal void BindVertexArray(uint vertexArray)
        {
            if (vertexArray != this.vertexArray)
            {
                this.Context.BindVertexArray(vertexArray);
                this.vertexArray = vertexArray;
            }
        }

        public void BlendFunc(BlendingFactorSrc sfactor, BlendingFactorDest dfactor)
        {
            this.Context.BlendFunc(sfactor, dfactor);
        }

        public void Clear(ClearBufferMask mask)
        {
            this.Context.Clear(mask);
        }

        public void ClearColor(Color4 color)
        {
            this.Context.ClearColor(color);
        }

        public void Disable(EnableCap cap)
        {
            this.Context.Disable(cap);
        }

        public void DrawArrays(VertexArray vertexArray, PrimitiveType mode, int first, int count)
        {
            if (vertexArray == null)
                throw new ArgumentNullException("vertexArray");

            this.BindVertexArray(vertexArray.Handle);
            this.Context.DrawArrays(mode, first, count);
        }

        public void DrawElements<TBuffer>(VertexArray vertexArray, IndexBuffer<TBuffer> indexBuffer, PrimitiveType mode, int count)
            where TBuffer : struct
        {
            if (vertexArray == null)
                throw new ArgumentNullException("vertexArray");

            if (indexBuffer == null)
                throw new ArgumentNullException("indexBuffer");

            this.BindVertexArray(vertexArray.Handle);
            this.BindBuffer(BufferTarget.ElementArrayBuffer, indexBuffer.Handle);
            this.Context.DrawElements(mode, count, indexBuffer.DrawElementType, IntPtr.Zero);
        }
        
        public void Enable(EnableCap cap)
        {
            this.Context.Enable(cap);
        }

        public void PushScissorBox(Viewport scissorBox)
        {
            this.scissorBoxStack.Push(scissorBox);
            this.UpdateScissorBox();
        }

        public void PopScissorBox()
        {
            this.scissorBoxStack.Pop();
            this.UpdateScissorBox();
        }
        
        public void SetActiveTexture(uint activeTexture)
        {
            if ((uint)activeTexture != this.activeTexture)
            {
                this.Context.ActiveTexture(GL.Texture0 + activeTexture);
                this.activeTexture = (uint)activeTexture;
            }            
        } 

        public bool SwapBuffers()
        {
            return this.Context.SwapBuffers();
        }

        private void UpdateScissorBox()
        {
            Viewport viewport;

            if (this.scissorBoxStack.Count > 0)
            {
                viewport = this.scissorBoxStack.Peek();
            }
            else
            {
                viewport = this.Viewport;
            }

            this.Context.Scissor(viewport.X, viewport.Y, viewport.Width, viewport.Height);
        }        
    }
}
